Unions and interfaces
Union 和 interface 是抽象类型,它们允许一个 schema 字段,返回多个类型中的某一种类型。
Union
当你定义一个 union 类型时,你声明的对象类型可以被包含在 union 中。
union Media = Book | Movie
一个字段可以将一个 union 类型(或者一个 union 类型的列表),作为它的返回值类型。它可以返回任一被包含在 union 中的对象类型。
type Query {
allMedia: [Media] # This list can include both Book and Movie objects
}
union 类型所包含的所有类型,必须是对象类型(不能是 scalar、input 类型)。被包含的类型之间,不需要共享任何字段。
私货:被包含的类型之间,可以是毫无关联的类型,字段不需要有相似性。
例子
下面的 schema 定义了一个名为SearchResult
的 union 类型。它可以返回一本Book
或一位Author
:
union SearchResult = Book | Author
type Book {
title: String!
}
type Author {
name: String!
}
type Query {
search(contains: String): [SearchResult!]
}
SearchResult
类型允许Query.search
返回一个包含Book
s 和Author
s 的列表。
查询一个 union
如果一个返回值的类型,是 union 时,GraphQL 客户端不知道字段将返回哪个对象类型。为此,一个查询可以包含多种可能类型的子字段。
下面是一个查询 schema 的示例:
query GetSearchResults {
search(contains: "Shakespeare") {
# Querying for __typename is almost always recommended,
# but it's even more important when querying a field that
# might return one of multiple types.
__typename
... on Book {
title
}
... on Author {
name
}
}
}
这个查询使用 inline fragment 语法去查询SearchResult
的title
(如果他是Book
)或name
(如果他是Author
)。web 客户端可以被 passing the possibleTypes option告知关于这个多态的关系。
私货:passing possibleTypes option 是个啥?
下面是前面查询的返回结果:
{
"data": {
"search": [
{
"__typename": "Book",
"title": "The Complete Works of William Shakespeare"
},
{
"__typename": "Author",
"name": "William Shakespeare"
}
]
}
}
解析一个 union
阅读本章节前,建议先了解resolver
为了全面解析 union,Apollo Server 需要确认 union 将会返回哪一种类型。为此,你需要在 resolver map 中,为 union 定义一个__resolveType
函数。
__resolveType
函数负责确定一个对象的 GraphQL 返回类型并且以字符串的形式返回类型名称。可以通过任意逻辑来完成这件事,例如:
- 检查字段上的某具有唯一特征的值是否存在
- 如果 JavaScript 对象的类型与 GraphQL 的类型相关连,则使用
instanceOf
。
下面是一个为SearchResult
定义的__resovleType
函数:
const resolvers = {
SearchResult: {
__resolveType(obj, contextValue, info){
// Only Author has a name field
if(obj.name){
return 'Author';
}
// Only Book has a title field
if(obj.title){
return 'Book';
}
return null; // GraphQLError is thrown
},
},
Query: {
search: () => { ... }
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server ready at: ${url}`);
如果
__resolveType
函数返回一个与 schema 类型定义不相符的值,与其相关的 operation 将产生一个 GraphQL 错误。
interface
一个 interface 确定了多个对象类型都可以包含的一批字段:
interface Book {
title: String!
author: Author!
}
如果一个对象类型implements
了一个 interface,它一定可以具备 interface 所有的字段:
type Textbook implements Book {
title: String! # Must be present
author: Author! # Must be present
courses: [Course!]!
}
一个字段可以将一个 interface(或者一个 interface 的列表)作为返回类型。在这种情况下,这个字段可以返回任意一个implements
了这个 interface 的对象类型:
type Query {
books: [Book!]! # Can include Textbook objects
}
例子
下面的 schema 中定义了名为Book
的 interface,并且有两个对象类型 implements 了它:
interface Book {
title: String!
author: Author!
}
type Textbook implements Book {
title: String!
author: Author!
courses: [Course!]!
}
type ColoringBook implements Book {
title: String!
author: Author!
colors: [String!]!
}
type Query {
books: [Book!]!
}
在这个 scheme 中,Query.books
可以返回包含Textbook
和ColoringBook
的列表。
查询一个 interface
如果一个字段的类型是 interface,客户端可以查询这个 interface 的字段中的任意字段:
query GetBooks {
books {
title
author
}
}
客户端也可以查询这个 interface 没有包含的字段:
query GetBooks {
books {
# Querying for __typename is almost always recommended,
# but it's even more important when querying a field that
# might return one of multiple types.
__typename
title
... on Textbook {
courses {
# Only present in Textbook
name
}
}
... on ColoringBook {
colors # Only present in ColoringBook
}
}
}
这个查询使用内联片段去查询Book
的courses
字段(如果它属于Textbook
类型)或者colors
字段(如果它属于ColoringBook
类型)。通过passing the possibleTypes option,web 客户端可以被告知关于这个多态关系。
下面是前面查询的一个有效结果:
{
"data": {
"books": [
{
"__typename": "Textbook",
"title": "Wheelock's Latin",
"courses": [
{
"name": "Latin I"
}
]
},
{
"__typename": "ColoringBook",
"title": "Oops All Water",
"colors": ["Blue"]
}
]
}
}
解析一个 interface
阅读本章节前,建议了解resolver
与 union 类型相同,Apollo Server 需求 interface 定义一个__resolveType
函数去确定那个哪一种对象类型将会被返回。
下面是一个Book
的__resolveType
函数的例子:
const resolvers = {
Book: {
__resolveType(book, contextValue, info){
// Only Textbook has a courses field
if(book.courses){
return 'Textbook';
}
// Only ColoringBook has a colors field
if(book.colors){
return 'ColoringBook';
}
return null; // GraphQLError is thrown
},
},
Query: {
books: () => { ... }
},
};